knitr::opts_chunk$set(results='hide', message=FALSE, warning=FALSE)
Load libraries…
Function to get EEA data…
eeaGet <- function(query){
tryCatch(return({
GET("https://discodata.eea.europa.eu/sql",
query = list(query=query))%>%
content(as="parsed") %>%
(function(x){x$results}) %>%
spread_all
}), error = function(e){print(paste("problem retrieving",query))})
}
Functions to clean up the harmonized manufacturer names…
eeaCleanNames <- function(df){ # just a function to rewrite some of these names
df %>%
mutate(name = toupper(Mh)) %>%
mutate(name = str_replace(name, "GENERAL MOTORS?", "GM")) %>%
mutate(name = str_replace(name, "LAND ROVER","LAND-ROVER")) %>%
mutate(name = str_replace(name, "-WERKE|-AMG","")) %>%
mutate(name = str_replace(name, "ROLLS ROYCE","ROLLS-ROYCE")) %>%
mutate(name = str_replace(name, "VEHICULES ELECTRIQUES PININFARINA-BOLLORE","PININFARINA-BOLLORE")) %>%
mutate(name = str_replace(name, "UNKNWON","UNKNOWN")) %>%
mutate(name = str_replace(name, "GREAT WALL","GREAT-WALL")) %>%
mutate(name = str_replace(name, "SHANGHAI MAPLE","MAPLE")) %>%
mutate(name = str_replace(name, "^ROVER","TATA")) %>%
mutate(name = str_replace(name, "AUTOMOBILES?\\s?","")) %>%
mutate(name = str_replace(name, "MOTOR CORPORATION\\s?","")) %>%
mutate(name = str_replace(name, "(GENERAL )MOTOR COMPANY\\s?","")) %>%
mutate(name = str_replace(name, "MOTORS?\\s?","")) %>%
mutate(name = if_else(str_detect(Mh, "SHUANGHUAN"), "SHUANGHUAN", name)) %>%
mutate(name = if_else(str_detect(Mh, "BAYERISCHE MOTOREN WERKE AG"), "BMW", name)) %>%
mutate(first = str_extract(name, "(^[^\\s]*)(\\s|$)")) %>%
mutate(first = str_squish(str_trim(first))) %>%
mutate(man.name.clean = first, first = NULL, name = NULL)
}
eeaRegroupByCleanNames <- function(df){
df %>% # renamed cleanly
group_by(man.name.clean, year) %>%
summarise(mass_avg = weighted.mean(mass_avg, n_sales), # and then re-group and re-summarize according to the new names
wltp_avg = weighted.mean(wltp_avg, n_sales),
enedc_avg = weighted.mean(enedc_avg, n_sales),
n_sales = sum(n_sales)) %>%
ungroup
}
# looking at specific odd names
# mass.tbl %>%
# filter(str_detect(Mh, "LIFAN"))
Functions to make the bubble graphs…
eeaBubbleGraph <- function(df, x, y, title){ # we'd like to make bubble size a var as well, but it's complicated!
# first, the fit to be able to label them in the graphs:
df %>%
nest(-year) %>% # fold everything by year
filter(year>2016) %>% # use only years after 2016, because no emissions data before then
mutate(fit = map(data, ~lm(paste(y,"~",x), data = ., weights = n_sales)),
results = map(fit, tidy)) %>% # fit and clean the fit for each year
unnest(results) %>% # unfold the fit results
pivot_wider(id_cols=c(year, data), values_from = estimate, names_from = term) %>% # make a column for each fit result of interest
rename(slope=mass_avg, intercept=`(Intercept)`) %>% #
mutate(x_max = map(data, ~(max(.$mass_avg)))) %>%
unnest(x_max) %>%
mutate(y_max = x_max*slope + intercept, x_max = x_max-100, man.name.clean = "name", mass_avg = 1, wltp_avg = 1, n_sales = 1) ->
emissions.ratio
# then, make the actual plots:
fig <- (ggplot(filter(mass.tbl.clean, year>2016), aes_string(label="man.name.clean", x=x, y=y, size="n_sales")) +
facet_grid(cols = vars(year),drop = T) +
geom_point(alpha=0.5) +
ylab("Specific emissions fleet average (g/km)") +
xlab("Average vehicle mass (kg)") +
labs(size = "Sales by manufacturer") +
ggtitle(title) +
geom_smooth(method = "lm", mapping = aes(weight = n_sales)) +
#stat_smooth(aes(outfit=fit<<-..y..))+
geom_text(data=emissions.ratio, aes(x=x_max, y=y_max, label=round(slope, digits = 3)), size=5, nudge_x = -100)) %>%
ggplotly %>%
layout(automargin=T) %>%
show
}
Getting the data
“Mh” seems to have the fewest missing values, and also the fewest distinct categories, so lets use that one. We’ll fetch average mass and average emissions by year and “harmonized manufacturer name” (Mh).
Get aggregate European data…
mass.tbl <- eeaGet("SELECT year, Mh,
AVG(CAST([m (kg)] AS FLOAT)) mass_avg,
AVG(CAST([Ewltp (g/km)] AS FLOAT)) wltp_avg,
AVG(CAST([Enedc (g/km)] AS FLOAT)) enedc_avg,
SUM(r) n_sales
FROM [CO2Emission].[latest].[co2cars]
GROUP BY year, Mh")
Clean up the European data…
mass.tbl %>%
eeaCleanNames %>%
eeaRegroupByCleanNames->
mass.tbl.clean
Get data just for Germany, nicely grouped…
mass.de.tbl <- eeaGet("SELECT year, Mh,
AVG(CAST([m (kg)] AS FLOAT)) mass_avg,
AVG(CAST([Ewltp (g/km)] AS FLOAT)) wltp_avg,
AVG(CAST([Enedc (g/km)] AS FLOAT)) enedc_avg,
SUM(r) n_sales
FROM [CO2Emission].[latest].[co2cars]
WHERE MS='DE'
GROUP BY year, Mh")
Clean up the German data…
mass.de.tbl %>%
eeaCleanNames %>%
eeaRegroupByCleanNames ->
mass.de.tbl.clean
Bubble graphs with weighted fits
Graphs for Europe
First, WLTP vs. mass:
eeaBubbleGraph(mass.tbl.clean, "mass_avg", "wltp_avg", "WLTP emissions v. mass (EU manufacturer fleet averages)")
Second, NEDC vs. mass:
eeaBubbleGraph(mass.tbl.clean, "mass_avg", "enedc_avg", "NEDC emissions v. mass (EU manufacturer fleet averages)")
Graphs for Germany
First, WLTP vs. mass:
eeaBubbleGraph(mass.de.tbl.clean, "mass_avg", "wltp_avg", "WLTP emissions v. mass (DE manufacturer fleet averages)")
Second, NEDC vs. mass:
eeaBubbleGraph(mass.de.tbl.clean, "mass_avg", "enedc_avg", "NEDC emissions v. mass (DE manufacturer fleet averages)")
LS0tDQp0aXRsZTogIkVtaXNzaW9ucyB2IE1hc3MgZnJvbSBFRUEiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7cn0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChyZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSkNCmBgYA0KDQpMb2FkIGxpYnJhcmllcy4uLg0KYGBge3IsIGVjaG89Rn0NCmxpYnJhcnkoaHR0cikNCmxpYnJhcnkodGlkeWpzb24pDQpsaWJyYXJ5KGRwbHlyKSAjIG11dGF0aW5nIGV0Yy4NCmxpYnJhcnkoc3RyaW5ncikgIyBtYW5pcHVsYXRpbmcgZS5nLiBtYW51ZmFjdHVyZXIgbmFtZXMNCmxpYnJhcnkodGlkeXIpICMgZm9yIGxtIGZpdHMgdG8gZ28gd2l0aCBmYWNldGVkIGdyYXBocw0KbGlicmFyeShwdXJycikgIyBmb3IgbG0gZml0cyB0byBnbyB3aXRoIGZhY2V0ZWQgZ3JhcGhzDQpsaWJyYXJ5KGJyb29tKSAjIGZvciBsbSBmaXRzIHRvIGdvIHdpdGggZmFjZXRlZCBncmFwaHMNCmxpYnJhcnkocGxvdGx5KSAjIGZvciB6b29tYWJsZS9pbnRlcmFjdGl2ZSBncmFwaGljczsgaW5jbHVkZXMgZ2dwbG90DQpgYGANCg0KRnVuY3Rpb24gdG8gZ2V0IEVFQSBkYXRhLi4uDQpgYGB7ciwgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQplZWFHZXQgPC0gZnVuY3Rpb24ocXVlcnkpew0KICB0cnlDYXRjaChyZXR1cm4oew0KICAgIEdFVCgiaHR0cHM6Ly9kaXNjb2RhdGEuZWVhLmV1cm9wYS5ldS9zcWwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgcXVlcnkgPSBsaXN0KHF1ZXJ5PXF1ZXJ5KSklPiUNCiAgICBjb250ZW50KGFzPSJwYXJzZWQiKSAlPiUNCiAgICAoZnVuY3Rpb24oeCl7eCRyZXN1bHRzfSkgJT4lDQogICAgc3ByZWFkX2FsbA0KICB9KSwgZXJyb3IgPSBmdW5jdGlvbihlKXtwcmludChwYXN0ZSgicHJvYmxlbSByZXRyaWV2aW5nIixxdWVyeSkpfSkNCn0NCmBgYA0KDQpGdW5jdGlvbnMgdG8gY2xlYW4gdXAgdGhlIGhhcm1vbml6ZWQgbWFudWZhY3R1cmVyIG5hbWVzLi4uDQpgYGB7ciwgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQplZWFDbGVhbk5hbWVzIDwtIGZ1bmN0aW9uKGRmKXsgIyBqdXN0IGEgZnVuY3Rpb24gdG8gcmV3cml0ZSBzb21lIG9mIHRoZXNlIG5hbWVzDQogIGRmICU+JQ0KICAgIG11dGF0ZShuYW1lID0gdG91cHBlcihNaCkpICU+JQ0KICAgIG11dGF0ZShuYW1lID0gc3RyX3JlcGxhY2UobmFtZSwgIkdFTkVSQUwgTU9UT1JTPyIsICJHTSIpKSAlPiUNCiAgICBtdXRhdGUobmFtZSA9IHN0cl9yZXBsYWNlKG5hbWUsICJMQU5EIFJPVkVSIiwiTEFORC1ST1ZFUiIpKSAlPiUNCiAgICBtdXRhdGUobmFtZSA9IHN0cl9yZXBsYWNlKG5hbWUsICItV0VSS0V8LUFNRyIsIiIpKSAlPiUNCiAgICBtdXRhdGUobmFtZSA9IHN0cl9yZXBsYWNlKG5hbWUsICJST0xMUyBST1lDRSIsIlJPTExTLVJPWUNFIikpICU+JQ0KICAgIG11dGF0ZShuYW1lID0gc3RyX3JlcGxhY2UobmFtZSwgIlZFSElDVUxFUyBFTEVDVFJJUVVFUyBQSU5JTkZBUklOQS1CT0xMT1JFIiwiUElOSU5GQVJJTkEtQk9MTE9SRSIpKSAlPiUgDQogICAgbXV0YXRlKG5hbWUgPSBzdHJfcmVwbGFjZShuYW1lLCAiVU5LTldPTiIsIlVOS05PV04iKSkgJT4lDQogICAgbXV0YXRlKG5hbWUgPSBzdHJfcmVwbGFjZShuYW1lLCAiR1JFQVQgV0FMTCIsIkdSRUFULVdBTEwiKSkgJT4lDQogICAgbXV0YXRlKG5hbWUgPSBzdHJfcmVwbGFjZShuYW1lLCAiU0hBTkdIQUkgTUFQTEUiLCJNQVBMRSIpKSAlPiUNCiAgICBtdXRhdGUobmFtZSA9IHN0cl9yZXBsYWNlKG5hbWUsICJeUk9WRVIiLCJUQVRBIikpICU+JQ0KICAgIG11dGF0ZShuYW1lID0gc3RyX3JlcGxhY2UobmFtZSwgIkFVVE9NT0JJTEVTP1xccz8iLCIiKSkgJT4lDQogICAgbXV0YXRlKG5hbWUgPSBzdHJfcmVwbGFjZShuYW1lLCAiTU9UT1IgQ09SUE9SQVRJT05cXHM/IiwiIikpICU+JQ0KICAgIG11dGF0ZShuYW1lID0gc3RyX3JlcGxhY2UobmFtZSwgIihHRU5FUkFMIClNT1RPUiBDT01QQU5ZXFxzPyIsIiIpKSAlPiUNCiAgICBtdXRhdGUobmFtZSA9IHN0cl9yZXBsYWNlKG5hbWUsICJNT1RPUlM/XFxzPyIsIiIpKSAlPiUNCiAgICBtdXRhdGUobmFtZSA9IGlmX2Vsc2Uoc3RyX2RldGVjdChNaCwgIlNIVUFOR0hVQU4iKSwgIlNIVUFOR0hVQU4iLCBuYW1lKSkgJT4lDQogICAgbXV0YXRlKG5hbWUgPSBpZl9lbHNlKHN0cl9kZXRlY3QoTWgsICJCQVlFUklTQ0hFIE1PVE9SRU4gV0VSS0UgQUciKSwgIkJNVyIsIG5hbWUpKSAlPiUNCiAgICBtdXRhdGUoZmlyc3QgPSBzdHJfZXh0cmFjdChuYW1lLCAiKF5bXlxcc10qKShcXHN8JCkiKSkgJT4lDQogICAgbXV0YXRlKGZpcnN0ID0gc3RyX3NxdWlzaChzdHJfdHJpbShmaXJzdCkpKSAlPiUNCiAgICBtdXRhdGUobWFuLm5hbWUuY2xlYW4gPSBmaXJzdCwgZmlyc3QgPSBOVUxMLCBuYW1lID0gTlVMTCkgDQp9DQoNCmVlYVJlZ3JvdXBCeUNsZWFuTmFtZXMgPC0gZnVuY3Rpb24oZGYpew0KICBkZiAlPiUgIyByZW5hbWVkIGNsZWFubHkNCiAgICBncm91cF9ieShtYW4ubmFtZS5jbGVhbiwgeWVhcikgJT4lDQogICAgc3VtbWFyaXNlKG1hc3NfYXZnID0gd2VpZ2h0ZWQubWVhbihtYXNzX2F2Zywgbl9zYWxlcyksICMgYW5kIHRoZW4gcmUtZ3JvdXAgYW5kIHJlLXN1bW1hcml6ZSBhY2NvcmRpbmcgdG8gdGhlIG5ldyBuYW1lcw0KICAgICAgICAgICAgICB3bHRwX2F2ZyA9IHdlaWdodGVkLm1lYW4od2x0cF9hdmcsIG5fc2FsZXMpLA0KICAgICAgICAgICAgICBlbmVkY19hdmcgPSB3ZWlnaHRlZC5tZWFuKGVuZWRjX2F2Zywgbl9zYWxlcyksDQogICAgICAgICAgICAgIG5fc2FsZXMgPSBzdW0obl9zYWxlcykpICU+JQ0KICAgIHVuZ3JvdXANCn0NCg0KIyBsb29raW5nIGF0IHNwZWNpZmljIG9kZCBuYW1lcw0KIyBtYXNzLnRibCAlPiUNCiMgICBmaWx0ZXIoc3RyX2RldGVjdChNaCwgIkxJRkFOIikpDQpgYGANCg0KDQpGdW5jdGlvbnMgdG8gbWFrZSB0aGUgYnViYmxlIGdyYXBocy4uLg0KYGBge3IsIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZWVhQnViYmxlR3JhcGggPC0gZnVuY3Rpb24oZGYsIHgsIHksIHRpdGxlKXsgIyB3ZSdkIGxpa2UgdG8gbWFrZSBidWJibGUgc2l6ZSBhIHZhciBhcyB3ZWxsLCBidXQgaXQncyBjb21wbGljYXRlZCENCg0KICAgICMgZmlyc3QsIHRoZSBmaXQgdG8gYmUgYWJsZSB0byBsYWJlbCB0aGVtIGluIHRoZSBncmFwaHM6IA0KICBkZiAlPiUNCiAgICBuZXN0KC15ZWFyKSAlPiUgIyBmb2xkIGV2ZXJ5dGhpbmcgYnkgeWVhcg0KICAgIGZpbHRlcih5ZWFyPjIwMTYpICU+JSAjIHVzZSBvbmx5IHllYXJzIGFmdGVyIDIwMTYsIGJlY2F1c2Ugbm8gZW1pc3Npb25zIGRhdGEgYmVmb3JlIHRoZW4NCiAgICBtdXRhdGUoZml0ID0gbWFwKGRhdGEsIH5sbShwYXN0ZSh5LCJ+Iix4KSwgZGF0YSA9IC4sIHdlaWdodHMgPSBuX3NhbGVzKSksDQogICAgICAgICAgIHJlc3VsdHMgPSBtYXAoZml0LCB0aWR5KSkgJT4lICMgZml0IGFuZCBjbGVhbiB0aGUgZml0IGZvciBlYWNoIHllYXINCiAgICB1bm5lc3QocmVzdWx0cykgJT4lICMgdW5mb2xkIHRoZSBmaXQgcmVzdWx0cw0KICAgIHBpdm90X3dpZGVyKGlkX2NvbHM9Yyh5ZWFyLCBkYXRhKSwgdmFsdWVzX2Zyb20gPSBlc3RpbWF0ZSwgbmFtZXNfZnJvbSA9IHRlcm0pICU+JSAjIG1ha2UgYSBjb2x1bW4gZm9yIGVhY2ggZml0IHJlc3VsdCBvZiBpbnRlcmVzdA0KICAgIHJlbmFtZShzbG9wZT1tYXNzX2F2ZywgaW50ZXJjZXB0PWAoSW50ZXJjZXB0KWApICU+JSAjIA0KICAgIG11dGF0ZSh4X21heCA9IG1hcChkYXRhLCB+KG1heCguJG1hc3NfYXZnKSkpKSAlPiUgDQogICAgdW5uZXN0KHhfbWF4KSAlPiUNCiAgICBtdXRhdGUoeV9tYXggPSB4X21heCpzbG9wZSArIGludGVyY2VwdCwgeF9tYXggPSB4X21heC0xMDAsIG1hbi5uYW1lLmNsZWFuID0gIm5hbWUiLCBtYXNzX2F2ZyA9IDEsIHdsdHBfYXZnID0gMSwgbl9zYWxlcyA9IDEpIC0+DQogICAgZW1pc3Npb25zLnJhdGlvDQogIA0KICAjIHRoZW4sIG1ha2UgdGhlIGFjdHVhbCBwbG90czoNCiAgZmlnIDwtIChnZ3Bsb3QoZmlsdGVyKG1hc3MudGJsLmNsZWFuLCB5ZWFyPjIwMTYpLCBhZXNfc3RyaW5nKGxhYmVsPSJtYW4ubmFtZS5jbGVhbiIsIHg9eCwgeT15LCBzaXplPSJuX3NhbGVzIikpICsNCiAgICBmYWNldF9ncmlkKGNvbHMgPSB2YXJzKHllYXIpLGRyb3AgPSBUKSArDQogICAgZ2VvbV9wb2ludChhbHBoYT0wLjUpICsNCiAgICB5bGFiKCJTcGVjaWZpYyBlbWlzc2lvbnMgZmxlZXQgYXZlcmFnZSAoZy9rbSkiKSArDQogICAgeGxhYigiQXZlcmFnZSB2ZWhpY2xlIG1hc3MgKGtnKSIpICsgDQogICAgbGFicyhzaXplID0gIlNhbGVzIGJ5IG1hbnVmYWN0dXJlciIpICsNCiAgICBnZ3RpdGxlKHRpdGxlKSArDQogICAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgbWFwcGluZyA9IGFlcyh3ZWlnaHQgPSBuX3NhbGVzKSkgKw0KICAgICNzdGF0X3Ntb290aChhZXMob3V0Zml0PWZpdDw8LS4ueS4uKSkrDQogICAgZ2VvbV90ZXh0KGRhdGE9ZW1pc3Npb25zLnJhdGlvLCBhZXMoeD14X21heCwgeT15X21heCwgbGFiZWw9cm91bmQoc2xvcGUsIGRpZ2l0cyA9IDMpKSwgc2l6ZT01LCBudWRnZV94ID0gLTEwMCkpICU+JQ0KICAgIGdncGxvdGx5ICU+JQ0KICAgIGxheW91dChhdXRvbWFyZ2luPVQpICU+JQ0KICAgIHNob3cNCn0NCmBgYA0KDQojIyMgR2V0dGluZyB0aGUgZGF0YQ0KDQoiTWgiIHNlZW1zIHRvIGhhdmUgdGhlIGZld2VzdCBtaXNzaW5nIHZhbHVlcywgYW5kIGFsc28gdGhlIGZld2VzdCBkaXN0aW5jdCBjYXRlZ29yaWVzLCBzbyBsZXRzIHVzZSB0aGF0IG9uZS4NCldlJ2xsIGZldGNoIF9hdmVyYWdlIG1hc3NfIGFuZCBfYXZlcmFnZSBlbWlzc2lvbnNfIGJ5IF95ZWFyXyBhbmQgXyJoYXJtb25pemVkIG1hbnVmYWN0dXJlciBuYW1lIiAoTWgpXy4NCg0KDQpHZXQgYWdncmVnYXRlIEV1cm9wZWFuIGRhdGEuLi4NCmBgYHtyLCByZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm1hc3MudGJsIDwtIGVlYUdldCgiU0VMRUNUIHllYXIsIE1oLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFWRyhDQVNUKFttIChrZyldIEFTIEZMT0FUKSkgbWFzc19hdmcsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQVZHKENBU1QoW0V3bHRwIChnL2ttKV0gQVMgRkxPQVQpKSB3bHRwX2F2ZywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFWRyhDQVNUKFtFbmVkYyAoZy9rbSldIEFTIEZMT0FUKSkgZW5lZGNfYXZnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgU1VNKHIpIG5fc2FsZXMNCiAgICAgICAgICAgICAgICAgICAgICBGUk9NIFtDTzJFbWlzc2lvbl0uW2xhdGVzdF0uW2NvMmNhcnNdDQogICAgICAgICAgICAgICAgICAgICAgR1JPVVAgQlkgeWVhciwgTWgiKQ0KYGBgDQoNCkNsZWFuIHVwIHRoZSBFdXJvcGVhbiBkYXRhLi4uDQpgYGB7ciwgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQptYXNzLnRibCAlPiUgDQogIGVlYUNsZWFuTmFtZXMgJT4lDQogIGVlYVJlZ3JvdXBCeUNsZWFuTmFtZXMtPg0KbWFzcy50YmwuY2xlYW4NCmBgYA0KDQpHZXQgZGF0YSBqdXN0IGZvciBHZXJtYW55LCBuaWNlbHkgZ3JvdXBlZC4uLg0KYGBge3IsIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbWFzcy5kZS50YmwgPC0gZWVhR2V0KCJTRUxFQ1QgeWVhciwgTWgsDQpBVkcoQ0FTVChbbSAoa2cpXSBBUyBGTE9BVCkpIG1hc3NfYXZnLCANCkFWRyhDQVNUKFtFd2x0cCAoZy9rbSldIEFTIEZMT0FUKSkgd2x0cF9hdmcsDQpBVkcoQ0FTVChbRW5lZGMgKGcva20pXSBBUyBGTE9BVCkpIGVuZWRjX2F2ZywNClNVTShyKSBuX3NhbGVzDQpGUk9NIFtDTzJFbWlzc2lvbl0uW2xhdGVzdF0uW2NvMmNhcnNdDQpXSEVSRSBNUz0nREUnDQpHUk9VUCBCWSB5ZWFyLCBNaCIpDQpgYGANCg0KQ2xlYW4gdXAgdGhlIEdlcm1hbiBkYXRhLi4uDQpgYGB7ciwgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQptYXNzLmRlLnRibCAlPiUNCiAgZWVhQ2xlYW5OYW1lcyAlPiUNCiAgZWVhUmVncm91cEJ5Q2xlYW5OYW1lcyAtPg0KICBtYXNzLmRlLnRibC5jbGVhbg0KYGBgDQoNCiMjIEJ1YmJsZSBncmFwaHMgd2l0aCB3ZWlnaHRlZCBmaXRzDQoNCiMjIyBHcmFwaHMgZm9yIEV1cm9wZQ0KDQpGaXJzdCwgV0xUUCB2cy4gbWFzczoNCmBgYHtyLCByZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTV9DQplZWFCdWJibGVHcmFwaChtYXNzLnRibC5jbGVhbiwgIm1hc3NfYXZnIiwgIndsdHBfYXZnIiwgIldMVFAgZW1pc3Npb25zIHYuIG1hc3MgKEVVIG1hbnVmYWN0dXJlciBmbGVldCBhdmVyYWdlcykiKQ0KYGBgDQpTZWNvbmQsIE5FREMgdnMuIG1hc3M6DQpgYGB7ciwgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD01fQ0KZWVhQnViYmxlR3JhcGgobWFzcy50YmwuY2xlYW4sICJtYXNzX2F2ZyIsICJlbmVkY19hdmciLCAiTkVEQyBlbWlzc2lvbnMgdi4gbWFzcyAoRVUgbWFudWZhY3R1cmVyIGZsZWV0IGF2ZXJhZ2VzKSIpDQpgYGANCg0KIyMjIEdyYXBocyBmb3IgR2VybWFueQ0KRmlyc3QsIFdMVFAgdnMuIG1hc3M6DQpgYGB7ciwgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD01fQ0KZWVhQnViYmxlR3JhcGgobWFzcy5kZS50YmwuY2xlYW4sICJtYXNzX2F2ZyIsICJ3bHRwX2F2ZyIsICJXTFRQIGVtaXNzaW9ucyB2LiBtYXNzIChERSBtYW51ZmFjdHVyZXIgZmxlZXQgYXZlcmFnZXMpIikNCmBgYA0KU2Vjb25kLCBORURDIHZzLiBtYXNzOg0KYGBge3IsIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NX0NCmVlYUJ1YmJsZUdyYXBoKG1hc3MuZGUudGJsLmNsZWFuLCAibWFzc19hdmciLCAiZW5lZGNfYXZnIiwgIk5FREMgZW1pc3Npb25zIHYuIG1hc3MgKERFIG1hbnVmYWN0dXJlciBmbGVldCBhdmVyYWdlcykiKQ0KYGBg